//------------------------------------------------------------------------------
// Copyright (c) 2008 www.dnaide.com
// Licensed under the MIT (MIT-LICENSE.txt)
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Xml;

using DNAide;
using DNAide.Drawing;
using DNAide.Drawing.Quantization;
using DNAide.Web;
using DNAide.Configuration;
using DNAide.Core;
using DNAide.IO;

namespace DNAide.Web.UI.Controls
{

	/// <summary>
	/// </summary>
	public abstract class DNAideImageControl : DNAideControl
	{

		private bool _hasRunCreateOutput;
		private string _finalUrl;
		private string _finalPath;
		private string _finalInfoPath;
		private int _finalWidth;
		private int _finalHeight;
		private Bitmap _bitmapToTransform;
		private static object _lock = new object();

		#region Properties

		/// <summary>
		/// </summary>
		protected Boolean HasRunCreateOutput
		{
			get { return _hasRunCreateOutput; }
			set	{ _hasRunCreateOutput = value; }
		}

		/// <summary>
		/// </summary>
		protected String FinalPath
		{
			get { return _finalPath; }
			set { _finalPath = value; }
		}

		/// <summary>
		/// </summary>
		protected String FinalInfoPath
		{
			get 
			{
				if (string.IsNullOrEmpty(_finalInfoPath))
				{
					if (!string.IsNullOrEmpty(_finalPath))
					{
						if (!string.IsNullOrEmpty(OutputInfoDirectory))
						{
							if (!OutputInfoDirectory.Contains(@"\"))
							{
								_finalInfoPath = HttpContext.Current.Server.MapPath(OutputInfoDirectory);
							}
							else
							{
								_finalInfoPath = OutputInfoDirectory;
							}
						}
						else
						{
							_finalInfoPath = Path.Combine(Path.GetDirectoryName(_finalPath), ".info");
						}
						if (!Directory.Exists(_finalInfoPath))
						{
							Directory.CreateDirectory(_finalInfoPath);
						}
						_finalInfoPath = Path.Combine(_finalInfoPath, Path.GetFileNameWithoutExtension(_finalPath) + ".info");
					}
				}
				return _finalInfoPath;
			}
			set
			{
				_finalInfoPath = value;
			}
		}

		/// <summary>
		/// </summary>
		protected Bitmap BitmapToTransform
		{
			get { return _bitmapToTransform; }
			set { _bitmapToTransform = value; }
		}

		/// <summary>
		/// The final url used by the control. Set on pre-render
		/// </summary>
		public String FinalUrl
		{
			get
			{
				return _finalUrl;
			}
			protected set
			{
				_finalUrl = value;
			}
		}

		/// <summary>
		/// The final width of the image generated. Set on pre-render
		/// </summary>
		public Int32 FinalWidth
		{
			get
			{
				return _finalWidth;
			}
			protected set
			{
				_finalWidth = value;
			}
		}

		/// <summary>
		/// The final height of the image generated. Set on pre-render
		/// </summary>
		public Int32 FinalHeight
		{
			get
			{
				return _finalHeight;
			}
			protected set
			{
				_finalHeight = value;
			}
		}

		/// <summary>
		/// Output folder
		/// </summary>
		/// <remarks>
		/// Must be in the format of a local url than a path ie. ~/images/resized/ rather than C:\website\images.resized
		/// Default =  DNAide.Configuration.DNAideConfigurationSettings.Web.CacheUrl + "/Images";
		/// </remarks>
		public String OutputDirectoryUrl
		{
			get { return ((ViewState["OutputDirectoryUrl"] == null) ? string.Empty : (string)ViewState["OutputDirectoryUrl"]); }
			set { ViewState["OutputDirectoryUrl"] = value; }
		}

		/// <summary>
		/// The directory to hold image info files
		/// </summary>
		/// <remarks>
		/// Can be a url within the site or a file path e.g. ~/images/.info or c:\images.info
		/// Default = Same as OutputDirectoryUrl + ".info";
		/// </remarks>
		public String OutputInfoDirectory
		{
			get { return ((ViewState["OutputInfoDirectory"] == null) ? string.Empty : (string)ViewState["OutputInfoDirectory"]); }
			set { ViewState["OutputInfoDirectory"] = value; }
		}

		/// <summary>
		/// Output file name
		/// </summary>
		/// <remarks>
		/// Defaults:
		/// Will use the SourceFileName e.g. test.jpg would be saved as test_transformed.jpg
		/// If there is no SourceFileName i.e. you have set the source to load from a Stream or Bitmap, the output file name will be based on a new GUID
		/// </remarks>
		public String OutputFileName
		{
			get { return ((ViewState["OutputFileName"] == null) ? string.Empty : (string)ViewState["OutputFileName"]); }
			set { ViewState["OutputFileName"] = value; }
		}

		/// <summary>
		/// Ouput image type
		/// </summary>
		/// <remarks>
		/// Default = Jpeg
		/// </remarks>
		public ImageType OutputImageType
		{
			get { return ((ViewState["OutputImageType"] == null) ? ImageType.Jpeg : (ImageType)ViewState["OutputImageType"]); }
			set { ViewState["OutputImageType"] = value; }
		}

		/// <summary>
		/// If the output is to be a jpeg, the compression quality of the jpeg
		/// </summary>
		/// <remarks>
		/// Default = 60
		/// </remarks>
		public Int32 OutputJpegQuality
		{
			get { return ((ViewState["OutputJpegQuality"] == null) ? 60 : (Int32)ViewState["OutputJpegQuality"]); }
			set
			{
				if (value < 1 || value > 100)
				{
					value = 60;
				}
				ViewState["OutputJpegQuality"] = value;
			}
		}

		/// <summary>
		/// If the output is to be gif, the maximum number of colors in the gif palette
		/// </summary>
		/// <remarks>
		/// Default = 64
		/// </remarks>
		public Int32 OutputGifMaxColors
		{
			get { return ((ViewState["OutputGifMaxColors"] == null) ? 64 : (Int32)ViewState["OutputGifMaxColors"]); }
			set
			{
				if (value < 2 || value > 256)
				{
					value = 64;
				}
				ViewState["OutputGifMaxColors"] = value;
			}
		}

		/// <summary>
		/// If the output is to be a gif, you can choose which color will become transparent
		/// </summary>
		public Color OutputGifTransparentColor
		{
			get { return ((ViewState["OutputGifTransparentColor"] == null) ? Color.Transparent : (Color)ViewState["OutputGifTransparentColor"]); }
			set { ViewState["OutputGifTransparentColor"] = value; }
		}

		/// <summary>
		/// Output write option
		/// </summary>
		/// <remarks>
		/// Default = IgnoreIfExists
		/// </remarks>
		public ImageWriteOptionType OutputWriteOption
		{
			get { return ((ViewState["OutputWriteOption"] == null) ? ImageWriteOptionType.IgnoreIfExists : (ImageWriteOptionType)ViewState["OutputWriteOption"]); }
			set { ViewState["OutputWriteOption"] = value; }
		}

		/// <summary>
		/// Alternate Text
		/// </summary>
		public String AlternateText
		{
			get { return ((ViewState["AlternateText"] == null) ? string.Empty : (string)ViewState["AlternateText"]); }
			set { ViewState["AlternateText"] = value; }
		}

		#endregion

		/// <summary>
		/// Raised allowing the developer to apply transformations to the final output bitmap
		/// </summary>
		public event ApplyTransformationsEventHandler ApplyTransformations;

		/// <summary>
		/// Passes the XMLWriter writing the info file to a handler.
		/// Allows you to place custom values in the .info file
		/// </summary>
		public event WriteToInfoFileEventHandler WriteToInfoFile;


		/// <summary>
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="bitmapToTransform"></param>
		public delegate void ApplyTransformationsEventHandler(object sender, ref Bitmap bitmapToTransform);

		/// <summary>
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="xmlWriter"></param>
		public delegate void WriteToInfoFileEventHandler(object sender, XmlWriter xmlWriter);


		/// <summary>
		/// </summary>
		protected virtual void RaiseApplyTransformationsEvent()
		{
			if (ApplyTransformations != null)
			{
				ApplyTransformations(this, ref _bitmapToTransform);
			}
		}

		/// <summary>
		/// </summary>
		/// <param name="extension"></param>
		protected void SetCorrectOuputImageTypeFromExtension(string extension)
		{
			switch (extension.ToLower())
			{
				case ".gif":
					OutputImageType = ImageType.Gif;
					break;
				default:
					OutputImageType = ImageType.Jpeg;
					break;
			}
		}

		/// <summary>
		/// </summary>
		protected virtual void SetFinalUrlAndPath()
		{
			// Sort out the final output directory url
			if (string.IsNullOrEmpty(OutputDirectoryUrl))
			{
				_finalUrl = DNAide.Configuration.DNAideConfigurationSettings.Web.CacheUrl + "/Images";
			}
			else
			{
				_finalUrl = OutputDirectoryUrl;
			}
			_finalPath = HttpContext.Current.Server.MapPath(_finalUrl);
			if (!Directory.Exists(_finalPath))
			{
				Directory.CreateDirectory(_finalPath);
			}

			// Sort out final output filename
			string outputFileName = OutputFileName;
			if (string.IsNullOrEmpty(outputFileName))
			{
				outputFileName = System.Guid.NewGuid().ToString().ToLower();
			}

			// If the OutputImageType has not been set, 
			// then try to default type from either the source or output filename
			if (ViewState["OutputImageType"] == null)
			{
				string extension = "";
				if (!string.IsNullOrEmpty(outputFileName))
				{
					extension = Path.GetExtension(outputFileName).ToLower();
				}
				SetCorrectOuputImageTypeFromExtension(extension);
			}

			if (OutputImageType == ImageType.Gif)
			{
				if (!outputFileName.ToLower().EndsWith(".gif"))
				{
					outputFileName += ".gif";
				}
			}
			else
			{
				if (!outputFileName.ToLower().EndsWith(".jpg"))
				{
					outputFileName += ".jpg";
				}
			}

			_finalUrl = UrlAide.Resolve(Path.Combine(_finalUrl, outputFileName).Replace(@"\", "/"));
			_finalPath = Path.Combine(_finalPath, outputFileName);
		}


		/// <summary>
		/// </summary>
		protected virtual void SaveFile()
		{
			lock (_lock)
			{
				if (OutputImageType == ImageType.Jpeg)
				{
					BitmapAide.SaveAsJpegToFile(_bitmapToTransform, _finalPath, OutputJpegQuality);
				}
				else
				{
					BitmapAide.SaveAsGifToFile(_bitmapToTransform, _finalPath, OutputGifMaxColors, OutputGifTransparentColor);
				}
			}
		}


		/// <summary>
		/// </summary>
		/// <param name="width"></param>
		/// <param name="height"></param>
		protected virtual void SetFinalWidthAndHeight(int width, int height)
		{
			_finalWidth = width;
			_finalHeight = height;
			
			if (string.IsNullOrEmpty(_finalPath) || string.IsNullOrEmpty(_finalUrl))
				return;
			
			CreateInfoFile();
		}


		/// <summary>
		/// </summary>
		protected virtual void SetFinalWidthAndHeight()
		{
			_finalWidth = 0;
			_finalHeight = 0;

			if (string.IsNullOrEmpty(_finalPath) || string.IsNullOrEmpty(_finalUrl))
				return;

			if (!File.Exists(FinalInfoPath))
			{
				CreateInfoFile();
			}

			using (StreamReader sr = new StreamReader(FinalInfoPath))
			{
				using (XmlReader xr = XmlReader.Create(sr))
				{
					while (xr.Read())
					{
						if (xr.NodeType.Equals(XmlNodeType.Element))
						{
							if (xr.LocalName.Equals("imageInfo"))
							{
								_finalWidth = CastAide.AsInt32(xr.GetAttribute("width"));
								_finalHeight = CastAide.AsInt32(xr.GetAttribute("height"));
								break;
							}
						}
					}
				}
			}
		}


		/// <summary>
		/// </summary>
		private void CreateInfoFile()
		{
			if (string.IsNullOrEmpty(_finalPath))
				return;

			lock (_lock)
			{
				using (Bitmap bitmap = BitmapAide.Load(_finalPath))
				{
					if (bitmap != null)
					{
						using (XmlWriter xr = XmlWriter.Create(FinalInfoPath))
						{
							xr.WriteStartElement("imageInfo");
							xr.WriteAttributeString("width", bitmap.Width.ToString());
							xr.WriteAttributeString("height", bitmap.Height.ToString());
							xr.WriteAttributeString("type", OutputImageType.ToString());
							if (OutputImageType == ImageType.Gif)
							{
								xr.WriteAttributeString("quality", OutputGifMaxColors.ToString());
							}
							else
							{
								xr.WriteAttributeString("quality", OutputJpegQuality.ToString());
							}

							if (WriteToInfoFile != null)
							{
								WriteToInfoFile(this, xr);
							}

							xr.WriteEndElement();
							xr.Flush();
						}

						bitmap.Dispose();
					}
				}
			}
		}


		/// <summary>
		/// If need be, calling this will rerun the CreateOutput method.
		/// </summary>
		protected void ReRunCreateOutput()
		{
			_hasRunCreateOutput = false;
			CreateOutput();
		}


		/// <summary>
		/// </summary>
		protected virtual void CreateOutput()
		{
			if (_hasRunCreateOutput)
			{
				return;
			}
			_hasRunCreateOutput = true;

			_finalUrl = string.Empty;
			_finalWidth = 0;
			_finalHeight = 0;
		}


		#region Render Methods

		/// <summary>
		/// </summary>
		/// <param name="writer"></param>
		protected override void RenderStart(HtmlTextWriter writer)
		{
			if (!_hasRunCreateOutput)
			{
				CreateOutput();
			}
			writer.WriteBeginTag("img");
		}

		/// <summary>
		/// </summary>
		/// <param name="writer"></param>
		protected override void RenderEnd(HtmlTextWriter writer)
		{
			writer.Write(" />");
			DisposeImageToTransform();
		}

		/// <summary>
		/// </summary>
		/// <param name="writer"></param>
		protected override void RenderAttributes(HtmlTextWriter writer)
		{
			this.Attributes.Add("src", _finalUrl);
			this.Attributes.Add("width", _finalWidth.ToString());
			this.Attributes.Add("height", _finalHeight.ToString());
			this.Attributes.Add("alt", HttpContext.Current.Server.HtmlEncode(AlternateText));

			this.Attributes.Remove("AlternateText");
			this.Attributes.Remove("BitmapToTransform");
			this.Attributes.Remove("FinalHeight");
			this.Attributes.Remove("FinalInfoPath");
			this.Attributes.Remove("FinalUrl");
			this.Attributes.Remove("FinalWidth");
			this.Attributes.Remove("HasRunCreateOutput");
			this.Attributes.Remove("OutputDirectoryUrl");
			this.Attributes.Remove("OutputFileName");
			this.Attributes.Remove("OutputGifMaxColors");
			this.Attributes.Remove("OutputGifTransparentColor");
			this.Attributes.Remove("OutputImageType");
			this.Attributes.Remove("OutputInfoDirectory");
			this.Attributes.Remove("OutputJpegQuality");
			this.Attributes.Remove("OutputWriteOption");
			
			base.RenderAttributes(writer);
		}

		#endregion

		/// <summary>
		/// </summary>
		protected void DisposeImageToTransform()
		{
			if (_bitmapToTransform != null)
			{
				_bitmapToTransform.Dispose();
				_bitmapToTransform = null;
			}
		}

	}
}
